/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once

#include "BufferStream.h"
#include "Ray.h"

namespace OptixDotNet {
namespace Traversal {
	/// <summary>
	/// The type of ray query to be performed during a batch raycast.
    /// </summary>
	public enum class QueryType 
	{ 
		/// <summary>
		/// Perform an any hit ray intersection
		/// </summary>
		AnyHit		= RTU_QUERY_TYPE_ANY_HIT,

		/// <summary>
		/// Perform a closest hit ray intersection
		/// </summary>
		ClosestHit	= RTU_QUERY_TYPE_CLOSEST_HIT,
	};

	/// <summary>
	/// The input format of the ray vector.
    /// </summary>
	public enum class RayFormat 
	{
		/// <summary>
		/// Describes a ray format with interleaved origin, direction, tmin, and tmax
		/// e.g. ray {
		///		float3 origin;
		///		float3 dir;
		///		float tmin;
		///		float tmax;
		///	}
		/// </summary>
		OriginDirectionMinMaxInterleaved	 = RTU_RAYFORMAT_ORIGIN_DIRECTION_TMIN_TMAX_INTERLEAVED,

		/// <summary>
		/// Describes a ray format with interleaved origin, direction
		/// e.g. ray {
		///		float3 origin;
		///		float3 dir;
		///	}
		/// </summary>
		OriginDirectionInterleaved			 = RTU_RAYFORMAT_ORIGIN_DIRECTION_INTERLEAVED,
	};

	/// <summary>
	/// The input format of the triangles.
	/// TriangleSoup implies future use of SetTriangles while Mesh implies use of SetMesh.
    /// </summary>
	public enum class TriFormat
	{
		Mesh			= RTU_TRIFORMAT_MESH,
		TriangleSoup	= RTU_TRIFORMAT_TRIANGLE_SOUP,
	};

	/// <summary>
	/// Initialization options (static across life of traversal object).
	///
	/// The rtuTraverse API supports both running on the CPU and GPU.  When
	/// RTU_INITOPTION_NONE is specified GPU context creation is attempted.  If
	/// that fails (such as when there isn't an NVIDIA GPU part present, the CPU
	/// code path is automatically chosen.  Specifying RTU_INITOPTION_GPU_ONLY or
	/// RTU_INITOPTION_CPU_ONLY will only use the GPU or CPU modes without
	/// automatic transitions from one to the other.
	///
	/// RTU_INITOPTION_CULL_BACKFACE will enable back face culling during
	/// intersection.
	/// </summary>
	[System::FlagsAttribute]
	public enum class InitOptions
	{
		None			= RTU_INITOPTION_NONE,
		GpuOnly			= RTU_INITOPTION_GPU_ONLY,
		CpuOnly			= RTU_INITOPTION_CPU_ONLY,
		CullBackFace	= RTU_INITOPTION_CULL_BACKFACE
	};

	public enum class RunTimeOptions
	{
		NumThreads = RTU_OPTION_INT_NUM_THREADS
	};

	[System::FlagsAttribute]
	public enum class TraversalOutput  {
		None		= RTU_OUTPUT_NONE,

		/// <summary>
		/// Outputs a float3 normal
		/// </summary>
		Normal		= RTU_OUTPUT_NORMAL,
										 
		/// <summary>
		/// float2 [alpha, beta] (gamma implicit)
		/// </summary>
		BaryCentric	= RTU_OUTPUT_BARYCENTRIC,

		/// <summary>
		///  char   [1 | 0]
		/// </summary>
		BackFacing	= RTU_OUTPUT_BACKFACING
	};

	/// <summary>
	/// Structure encapsulating the result of a single ray query.
    /// </summary>
	public value class TraversalResult
	{
	public:
		/// <summary>
		/// Index of the interesected triangle, -1 for miss.
		/// </summary>
		int PrimitiveID;

		/// <summary>
		/// Ray t parameter of hit point.
		/// </summary>
		float T;
	};

	/// <summary>
	/// Encapsulates a BufferStream that is used to return a pointer to GPU data from a Map operation.
	/// </summary>
	generic< typename T > where T : value class
	public value class TraversalStream
	{
	public:
		TraversalStream( BufferStream^ stream )
		{
			Stream = stream;
		}

		property System::Int64 Length
		{
			System::Int64 get() { return Stream->Length / sizeof( T ); }
		} 

		void GetData( array<T>^ results )
		{
			if( results == nullptr )
				throw gcnew System::ArgumentNullException( "results", "Results array cannot be null." );

			if( ( results->Length * sizeof( T ) ) != Stream->Length )
				throw gcnew System::ArgumentOutOfRangeException( "results", "Results array must be able to hold entire TraversalStream" );

			Stream->ReadRange<T>( results );
		}



	public:
		BufferStream^ Stream;
	};

	/// <summary>
	/// Traversal API allowing batch raycasting queries utilizing either OptiX or the CPU.
    /// </summary>
	public ref class Traversal
	{
	public:

		/// <summary>
		/// Constructs an Optix Traversal object for batch raycasting
		/// </summary>
		Traversal( Context^ context, QueryType query, RayFormat rayFormat, TriFormat triFormat, TraversalOutput outputs, InitOptions options );
		~Traversal();
		!Traversal();

		/// <summary>
		/// Destroys the internal Optix resource.
        /// </summary>
		inline void Destroy()
		{
			if( mTraversal != 0 )
			{
				rtuTraversalDestroy( mTraversal );
				mTraversal = 0;
			}
		}

		/// <summary>
		/// Specify triangle mesh to be intersected by the next call to
   		/// Traverse.  Only one geometry set may be active at a time.
   		/// Subsequent calls to SetTriangles or SetMesh will
   		/// override any previously specified geometry.  No internal copies of the mesh
   		/// data are made.  The user should ensure that the mesh data remains valid
   		/// until after Traverse has been called.  Counter-clockwise
   		/// winding is assumed for normal and backfacing computations.
		/// </summary>
		/// <param name="numVerts">Number of vertices in the mesh</param>
		/// <param name="verts">Array of 32-bit float vertices comprising the mesh with layout: [ v1_x, v1_y, v1_z, v2.x, ... ]</param>
		/// <param name="numTris">Number of triangles in the mesh</param>
		/// <param name="indices">Array of 32-bit integer indices composing the mesh with layout: [tri_index0, tri_index1, tri_index2, ... ]</param>
		generic<typename T> where T : value class
		void SetMesh( unsigned int numVerts, array<T>^ verts, unsigned int numTris, array<unsigned int>^ indices );

		/// <summary>
		/// Specify triangle mesh to be intersected by the next call to
   		/// Traverse.  Only one geometry set may be active at a time.
   		/// Subsequent calls to SetTriangles or SetMesh will
   		/// override any previously specified geometry.  No internal copies of the mesh
   		/// data are made.  The user should ensure that the mesh data remains valid
   		/// until after Traverse has been called.  Counter-clockwise
   		/// winding is assumed for normal and backfacing computations.
		/// </summary>
		/// <param name="numVerts">Number of vertices in the mesh</param>
		/// <param name="verts">Array of 32-bit float vertices comprising the mesh with layout: [ v1_x, v1_y, v1_z, v2.x, ... ]</param>
		/// <param name="numTris">Number of triangles in the mesh</param>
		/// <param name="indices">Array of 32-bit integer indices composing the mesh with layout: [tri_index0, tri_index1, tri_index2, ... ]</param>
		generic<typename T, typename U> where T : value class
		void SetMesh( unsigned int numVerts, array<T>^ verts, unsigned int numTris, array<U>^ indices );

		/// <summary>
		/// Specify triangle soup to be intersected by the next call to
		/// Traverse.  Only one geometry set may be active at a time.
		/// Subsequent calls to SetTriangles or SetMesh will
		/// override any previously specified geometry.  No internal copies of the
		/// triangle data are made.  The user should ensure that the triangle data
		/// remains valid until after Traverse has been
		/// called. Counter-clockwise winding is assumed for normal and backfacing
		/// computations.
		/// </summary>
		/// <param name="tris">Array of triangles composing the mesh with layout: [ tri1_v1.x, tri1_v1.y, tr1_v1.z, tri1_v2.x, ... ]</param>
		generic<typename T> where T : value class
		void SetTriangles( array<T>^ tris );

		/// <summary>
		/// Specify set of rays to be cast upon next call to Traverse.
		/// Rays should be packed in the RayFormat described in the creation of the Traverser object. 
		/// Note that this call invalidates any existing results buffers until Traverse is called again.
		/// T must be a type with size and layout that matches Math::Ray (<seealso cref="OptixDotNet::Math::Ray"></seealso>) 
		/// or Math::RayMinMax (<seealso cref="OptixDotNet::Math::RayMinMax"></seealso>)
		/// </summary>
		/// <remarks> Must be paired with a call to UnmapRays() </remarks>
		/// <param name="numRays">The number of rays to be traced</param>
		/// <returns> Returns a writeable BufferStream pointing to the internal traversal ray data. NumRays must be copied into the stream.</returns>
		generic< typename T > where T : value class
		TraversalStream<T> MapRays( unsigned int numRays );

		/// <summary>
		/// Releases the mapping of the internal ray buffer. <seealso cref="MapRays"></seealso>
		/// </summary>
		void UnmapRays();

		/// <summary>
		/// Specify set of rays to be cast upon next call to Traverse.
		/// Note that this call invalidates any existing results buffers until Traverse is called again.
		/// </summary>
		void SetRayData( array<OptixDotNet::Math::Ray>^ rays );

		/// <summary>
		/// Specify set of rays to be cast upon next call to Traverse.
		/// Note that this call invalidates any existing results buffers until Traverse is called again.
		/// </summary>
		void SetRayData( array<OptixDotNet::Math::RayMinMax>^ rays );

		/// <summary>
		/// Specify set of rays to be cast upon next call to Traverse.
		/// Rays should be packed in the RayFormat described in the creation of the Traverser object. 
		/// T must be a type with size and layout that matches Math::Ray (<seealso cref="OptixDotNet::Math::Ray"></seealso>) 
		/// or Math::RayMinMax (<seealso cref="OptixDotNet::Math::RayMinMax"></seealso>)
		/// Note that this call invalidates any existing results buffers until Traverse is called again.
		/// </summary>
		generic< typename T > where T : value class
		void SetRayData( array<T>^ rays );

		/// <summary>
		/// Retrieve results of last Traverse call.  Results can be copied from the
		/// pointer returned by TraversalStream and will have length [NumRays] as prescribed from the previous call to MapRays.
		/// results. Returned primitive ID of -1 indicates a ray miss.
		/// </summary>
		/// <remarks> Must be paired with a call to UnmapResults() </remarks>
		/// <returns> Returns a readable <see cref="OptixDotNet::Traversal::TraversalStream{T}">TraversalResults</see> pointing to the internal traversal results data.</returns>
		TraversalStream<TraversalResult> MapResults();

		/// <summary>
		/// Releases the mapping of the internal results buffer. <seealso cref="MapResults"></seealso>
		/// </summary>
		void UnmapResults();

		/// <summary>
		/// Retrieve results of last Traverse call. Returned primitive ID of -1 indicates a ray miss.
		/// </summary>
		/// <param name="results">TraversalResult array, of lenght [NumRays] that will receive the results of the last Traverse.</param>
		void GetResults( array<TraversalResult>^ results );
 
		/// <summary>
		/// Retrieve Barycentric output from last Traverse call. Output can be
		/// copied from the Traversal stream and will have length [NumRays] as prescribed from the previous call to MapRays.
		/// </summary>
		/// <remarks> 
		/// Must be paired with a call to UnmapBaryCentricOutput(). 
		/// If Barycentric output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <returns>Returns a readable TraversalStream pointing to the internal float2 barycentric output data.</returns>
		generic< typename T > where T : value class
		TraversalStream<T> MapBaryCentricOutput();

		/// <summary>
		/// Releases the mapping of the internal results buffer. <seealso cref="MapBaryCentricOutput"></seealso>
		/// </summary>
		void UnmapBaryCentricOutput();

		/// <summary>
		/// Retrieve Barycentric output from last Traverse call.
		/// </summary>
		/// <remarks> 
		/// If Barycentric output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <param name="output">Vector2 array, of length [NumRays] that will receive the BaryCentric output from the last Traverse.</param>
		void GetBaryCentricOutput( array<OptixDotNet::Math::Vector2>^ output );

		/// <summary>
		/// Retrieve Normals output from last Traverse call. Output can be
		/// copied from the Traversal stream and will have length [NumRays] as prescribed from the previous call to MapRays.
		/// </summary>
		/// <remarks> 
		/// Must be paired with a call to UnmapNormalOutput(). 
		/// If Normals output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <returns>Returns a readable TraversalStream pointing to the internal float3 Normals output data.</returns>
		generic< typename T > where T : value class
		TraversalStream<T> MapNormalOutput();

		/// <summary>
		/// Releases the mapping of the internal results buffer. <seealso cref="MapNormalOutput"></seealso>
		/// </summary>
		void UnmapNormalOutput();

		/// <summary>
		/// Retrieve Normals output from last Traverse call.
		/// </summary>
		/// <remarks> 
		/// If Normals output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <param name="output">Vector2 array, of length [NumRays] that will receive the Normals output from the last Traverse.</param>
		void GetNormalOutput( array<OptixDotNet::Math::Vector3>^ output );

		/// <summary>
		/// Retrieve BackFacing output from last Traverse call. Output can be
		/// copied from the Traversal stream and will have length [NumRays] as prescribed from the previous call to MapRays.
		/// </summary>
		/// <remarks> 
		/// Must be paired with a call to UnmapBackFacingOutput(). 
		/// If BackFacing output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <returns>Returns a readable TraversalStream pointing to the internal byte BackFacing output data.</returns>
		TraversalStream<char> MapBackFacingOutput();

		/// <summary>
		/// Releases the mapping of the internal results buffer. <seealso cref="MapBackFacingOutput"></seealso>
		/// </summary>
		void UnmapBackFacingOutput();

		/// <summary>
		/// Retrieve BackFacing output from last Traverse call.
		/// </summary>
		/// <remarks> 
		/// If BackFacing output type was not turned on with the construction of the Traversal object an error will be returned<seealso cref="Traversal"></seealso>.
		/// </remarks>
		/// <param name="output">Vector2 array, of length [NumRays] that will receive the BackFacing output from the last Traverse.</param>
		void GetBackFacingOutput( array<char>^ output );

		/// <summary>
		/// Perform any necessary preprocessing (eg, acceleration structure building,
		/// optix context compilation).  It is not necessary to call this function as
		/// Traverse() will call this internally as necessary.
		/// </summary>
		void Preprocess();

		/// <summary>
		/// Perform any necessary preprocessing (eg, acceleration structure building and kernel compilation ) 
		/// and cast current rays against current geometry.
		/// </summary>
		void Traverse();

		/// <summary>
		/// Sets the RunTimeOptions.NumThreads option specifiying how many CPU threads to use for execution. <seealso cref="OptixDotNet::Traversal::RunTimeOptions"></seealso>
		/// </summary>
		property int NumCpuThreads
		{
			void set( int num )
			{
				unsigned int x = num;
				CheckError( rtuTraversalSetOption( mTraversal, RTU_OPTION_INT_NUM_THREADS, (void*)(&x) ) );
			}
		}

		/// <summary>
		/// Gets the size of the acceleration data for the current traversal geometry.
		/// Will force an acceleration build if necessary.
		/// </summary>
		property Size AccelDataSize
		{
			Size get()
			{
				Size size;
				CheckError( rtuTraversalGetAccelDataSize( mTraversal, &size ) );
				return size;
			}
		}

		/// <summary>
		/// Gets or Sets the acceleration data for the current traversal geometry
		/// Input acceleration data should be result of <see cref="Traversal::AccelData"/> or 
		/// <see cref="OptixDotNet::Acceleration::Data"/> call.
		/// Getting will force an acceleration build if necessary.
		/// </summary>
		property BufferStream^ AccelData
		{
			BufferStream^ get();
			void set( BufferStream^ stream );
		}

	private:
		void CheckError( RTresult result ) {
			if( result != RT_SUCCESS ) {
				const char* message;
				rtuTraversalGetErrorString( mTraversal, result, &message );

				System::String^ msg = "Optix error: " + this->GetType() + ": ";
				msg += AnsiiToStr( message );
				throw gcnew System::Exception( msg );
			}
		}
		
	private:
		RTUtraversal mTraversal;

		RayFormat mRayFormat;
		int mRaySize;

		int mCurrentNumRays;
	};
}
}